package server;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import java.util.Random;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Arrays;

import java.io.File;
import java.io.IOException;
import java.io.FileOutputStream;
import java.io.DataOutputStream;

import java.sql.SQLException;

public class CommandsTest extends TestCase {

    final String testConfigFilename = "/tmp/cmtest.config";

    private static RandomUtils randomUtils = new RandomUtils();
    private static Random random = new Random();
    private ConfigurationManager config;
    private IPTablesCommMock iptables;
    private IPTablesCommMock expectedIptables;
    private MYSQLCommMock mySql;
    private MYSQLCommMock expectedSql;
    private Commands commands;

    public CommandsTest(String name) {
	super(name);
    }

    protected void setUp() throws Exception {
	File file = new File(testConfigFilename);
	file.delete();

	config = new ConfigurationManager(testConfigFilename);
	mySql = new MYSQLCommMock();
	expectedSql = new MYSQLCommMock();
	iptables = new IPTablesCommMock();
	expectedIptables = new IPTablesCommMock();
	commands = new Commands(config, mySql, iptables);

	mySql.rules.put("ActiveRules_In", new ArrayList<Rule>());
	mySql.rules.put("ActiveRules_Out", new ArrayList<Rule>());
	expectedSql.rules.put("ActiveRules_In", new ArrayList<Rule>());
	expectedSql.rules.put("ActiveRules_Out", new ArrayList<Rule>());
    }

    protected void tearDown() {
	File file = new File(testConfigFilename);
	file.delete();
    }

    //////////////////////////////////////////////////////////////////////
    ////////                          isOn                        ////////
    //////////////////////////////////////////////////////////////////////

    public void testIsOn() throws Exception {
        assertEquals(commands.isOn(), true);

        commands.turnOn();
        assertEquals(commands.isOn(), true);
        assertEquals(config.getIsFirewallOn(), true);

        commands.turnOff();
        assertEquals(commands.isOn(), false);
        assertEquals(config.getIsFirewallOn(), false);

        commands.turnOff();
        assertEquals(commands.isOn(), false);
        assertEquals(config.getIsFirewallOn(), false);

        commands.turnOn();
        assertEquals(commands.isOn(), true);
        assertEquals(config.getIsFirewallOn(), true);

        commands.turnOn();
        assertEquals(commands.isOn(), true);
        assertEquals(config.getIsFirewallOn(), true);
    }

    //////////////////////////////////////////////////////////////////////
    ////////                        turnOff                       ////////
    //////////////////////////////////////////////////////////////////////


    // Checks behaviour when the FW is off. The SQL should be updated but
    // iptables should remain constantly empty.
    public void testTurnOff() throws Exception {

        Rule rule = new Rule(1, "1.2.3.4", 80, Rule.permissionOpt.ALLOW);

	// add a rule which will be added to IPTables, to see that it's
	// flushed when we turnOff()
        commands.addRule(RulesTableType.IN, "Default", rule);
        assertEquals(iptables.currentInRules.size(), 1);
	assertEquals(iptables.totalAdd, 1);
	assertEquals(iptables.totalRemove, 0);
	 
        commands.turnOff();
	assertEquals(iptables.totalAdd, 1);
	assertEquals(iptables.totalFlush, 1);

        assertIptablesEmpty();
        assertEquals(commands.isOn(), false);
        assertEquals(config.getIsFirewallOn(), false);
        assertEquals(config.getIsLogActive(), config.isLogActiveDefault);
	assertEquals(commands.isLogEnabled(), false);

	// Enable and disable the log - this shouldn't change anything (except
	// for the log state of course)
        commands.enableLog();
	assertEquals(commands.isLogEnabled(), true);
        assertEquals(config.getIsFirewallOn(), false);
        assertEquals(config.getIsLogActive(), true);
        assertIptablesEmpty();
        commands.disableLog();
	assertEquals(commands.isLogEnabled(), false);
        assertEquals(config.getIsFirewallOn(), false);
        assertEquals(config.getIsLogActive(), false);
        assertIptablesEmpty();
	
	// add a rule and remove it. This should only affect the DB
        commands.addRule(RulesTableType.IN, "Default", rule);
        expectedSql.addRule(expectedSql.getTableName("Default", true), rule);
        assertEquals(expectedSql.rulesets, mySql.rulesets);
        assertIptablesEmpty();
	assertEquals(iptables.totalAdd, 1);

        commands.deleteRule(RulesTableType.IN, "Default", 1);
        expectedSql.deleteRule(expectedSql.getTableName("Default", true), 1);
        assertEquals(expectedSql.rulesets, mySql.rulesets);
        assertIptablesEmpty();
	assertEquals(iptables.totalRemove, 0);
    }

    // Check that turnOff reports an exception when IPTables fails.
    // It's not fair to check the system's state if this occurs, because
    // it's undefined.
    public void testTurnOff_Exception() throws Exception {
	iptables.failNextRequest();
        try {
            commands.turnOff();
            fail("testTurnOff_Exception: expected turnOff to throw " + 
                    "but it didn't");
        } catch (FirewallException e) {}
    }

    //////////////////////////////////////////////////////////////////////
    ////////        isLogEnabled, enableLog, disabledLog          ////////
    //////////////////////////////////////////////////////////////////////
    
    // Test the log functions under normal circumstances
    public void testLogFunctions() throws Exception {
	// The initial value is the default
	assertEquals(config.getIsLogActive(), config.isLogActiveDefault);
	assertEquals(commands.isLogEnabled(), config.getIsLogActive());

	addSomeProtocols();
        Rule ipInRule1 = new Rule(1, "1.1.1.1", 100, Rule.permissionOpt.ALLOW);
        Rule ipInRule2 = new Rule(3, "2.2.2.2", 200, Rule.permissionOpt.DENY);
        Rule ipOutRule1 = new Rule(1, "3.3.3.3", 300, Rule.permissionOpt.ALLOW);
        Rule ipOutRule2 = new Rule(3, "4.4.4.4", 400, Rule.permissionOpt.DENY);
	Rule contextInRule1 = new Rule(2, 80, "http", Rule.permissionOpt.DENY);
	Rule contextInRule2 = new Rule(4, 110, "pop3", Rule.permissionOpt.DENY);
	Rule contextOutRule1 = new Rule(2, 23, "telnet", Rule.permissionOpt.DENY);
	Rule contextOutRule2 = new Rule(4, 25, "smtp", Rule.permissionOpt.DENY);

	// Turn logging off and add a couple of rules

	commands.disableLog();
	assertState(false, true, true, true);

        commands.addRule(RulesTableType.IN, "Default", ipInRule1);
	expectedIptables.add(RulesTableType.IN, ipInRule1, false, null);
        expectedSql.addRule(expectedSql.getTableName("Default", true), ipInRule1);

        commands.addRule(RulesTableType.IN, "Default", contextInRule1);
	expectedIptables.add(RulesTableType.IN, contextInRule1, false, "http in");
        expectedSql.addRule(expectedSql.getTableName("Default", true), contextInRule1);

        commands.addRule(RulesTableType.IN, "Default", ipInRule2);
	expectedIptables.add(RulesTableType.IN, ipInRule2, false, null);
        expectedSql.addRule(expectedSql.getTableName("Default", true), ipInRule2);

        commands.addRule(RulesTableType.IN, "Default", contextInRule2);
	expectedIptables.add(RulesTableType.IN, contextInRule2, false, "pop3 in");
        expectedSql.addRule(expectedSql.getTableName("Default", true), contextInRule2);

        commands.addRule(RulesTableType.OUT, "Default", ipOutRule1);
	expectedIptables.add(RulesTableType.OUT, ipOutRule1, false, null);
        expectedSql.addRule(expectedSql.getTableName("Default", false), ipInRule1);

        commands.addRule(RulesTableType.OUT, "Default", contextOutRule1);
	expectedIptables.add(RulesTableType.OUT, contextOutRule1, false, "telnet out");
        expectedSql.addRule(expectedSql.getTableName("Default", false), contextInRule1);

        commands.addRule(RulesTableType.OUT, "Default", ipOutRule2);
	expectedIptables.add(RulesTableType.OUT, ipOutRule2, false, null);
        expectedSql.addRule(expectedSql.getTableName("Default", false), ipInRule2);

        commands.addRule(RulesTableType.OUT, "Default", contextOutRule2);
	expectedIptables.add(RulesTableType.OUT, contextOutRule2, false, "smtp out");
        expectedSql.addRule(expectedSql.getTableName("Default", false), contextInRule2);

	assertState(false, true, true, true);

	// Turn logging on - IPTables should change, SQL should not
	commands.enableLog();
	expectedIptables.enableLog();
	assertState(true, true, true, true);

	// Turn logging back off - IPTables should change again, SQL still shouldn't
	commands.disableLog();
	expectedIptables.disableLog();
	assertState(false, true, true, true);

	// Delete some rules, then one more time, on and off again.
        commands.deleteRule(RulesTableType.IN, "Default", 2);
        expectedSql.deleteRule(expectedSql.getTableName("Default", true), 2);
        expectedSql.deleteRuleComplete(true);
        expectedIptables.remove(RulesTableType.IN, 2);
	assertState(false, true, true, true);

	// Log back on
    	commands.enableLog();
	expectedIptables.enableLog();
	assertState(true, true, true, true);

	// And back off
    	commands.disableLog();
	expectedIptables.disableLog();
	assertState(false, true, true, true);
    }

    // Check that enableLog reports exceptions thrown by IPTables
    public void testEnableLog_exception() {
	iptables.failNextRequest();
        try {
	    commands.enableLog();
            fail("testEnableLog_exception: expected enableLog to throw " + 
                    "but it didn't");
        } catch (FirewallException e) {}
    }

    // Check that disableLog reports exceptions thrown by IPTables
    public void testDisableLog_exception() {
	iptables.failNextRequest();
        try {
	    commands.disableLog();
            fail("testDisableLog_exception: expected disableLog to throw " + 
                    "but it didn't");
        } catch (FirewallException e) {}
    }

    //////////////////////////////////////////////////////////////////////
    ////////                     checkUser                        ////////
    //////////////////////////////////////////////////////////////////////
    
    // test checkUser in normal circumstances
    public void testCheckUser() throws Exception {
	// Add some users
	Password p1 = new Password("simple", "simplepassword", Password.typeOpt.SIMPLE);
	Password p2 = new Password("simpler", "simplerpassword", Password.typeOpt.SIMPLE);
	Password p3 = new Password("expert", "expertpassword", Password.typeOpt.EXPERT);
	Password p4 = new Password("admin", "adminadmin", Password.typeOpt.EXPERT);
	mySql.passwords.add(p1);
	mySql.passwords.add(p2);
	mySql.passwords.add(p3);
	mySql.passwords.add(p4);

	assertEquals(commands.checkUser("simple", "simplepassword"), p1);
	assertEquals(commands.checkUser("simpler", "simplerpassword"), p2);
	assertEquals(commands.checkUser("expert", "expertpassword"), p3);
	assertEquals(commands.checkUser("admin", "adminadmin"), p4);
    }

    // test checkUser when giving an invalid password
    public void testCheckUser_invalidPassword() throws Exception {
	Password p1 = new Password("simple", "simplepassword", Password.typeOpt.SIMPLE);
	mySql.passwords.add(p1);

	try {
	    commands.checkUser("simple", "blah");
	    fail("testCheckUser_invalidPassword: expected checkUser to throw " + 
		    "but it didn't");
	} catch (InvalidLoginException e) {}
    }

    // test checkUser when giving an invalid username
    public void testCheckUser_invalidUser() throws Exception {
    	Password p1 = new Password("simple", "simplepassword", Password.typeOpt.SIMPLE);
	mySql.passwords.add(p1);

	try {
	    commands.checkUser("mooky", "blah");
	    fail("testCheckUser_invalidUser: expected checkUser to throw " + 
		    "but it didn't");
	} catch (InvalidLoginException e) {}
    }

    // test checkUser when the SQL fails
    public void testCheckUser_SQLException() throws Exception {
    	Password p1 = new Password("simple", "simplepassword", Password.typeOpt.SIMPLE);
	mySql.passwords.add(p1);

	mySql.failNextRequest();
	try {
	    commands.checkUser("simple", "simplepassword");
	    fail("testCheckUser_SQLException: expected checkUser to throw " + 
		    "but it didn't");
	} catch (FirewallException e) {}
    }

    //////////////////////////////////////////////////////////////////////
    ////////                    Rule manipulation                 ////////
    //////////////////////////////////////////////////////////////////////

    public void testRuleManipulation_noLogActiveRuleset() throws Exception {
	addSomeProtocols();
	commands.disableLog();
        Rule ipInRule1 = new Rule(1, "1.1.1.1", 100, Rule.permissionOpt.ALLOW);
        Rule ipInRule2 = new Rule(3, "2.2.2.2", 200, Rule.permissionOpt.DENY);
        Rule ipOutRule1 = new Rule(1, "3.3.3.3", 300, Rule.permissionOpt.ALLOW);
        Rule ipOutRule2 = new Rule(3, "4.4.4.4", 400, Rule.permissionOpt.DENY);
	Rule contextInRule1 = new Rule(2, 80, "http", Rule.permissionOpt.DENY);
	Rule contextInRule2 = new Rule(4, 110, "pop3", Rule.permissionOpt.DENY);
	Rule contextOutRule1 = new Rule(2, 23, "telnet", Rule.permissionOpt.DENY);
	Rule contextOutRule2 = new Rule(4, 25, "smtp", Rule.permissionOpt.DENY);

	commands.addRule(RulesTableType.IN, "Default", ipInRule1);
        expectedSql.addRule(expectedSql.getTableName("Default", true), ipInRule1);
	commands.addRule(RulesTableType.IN, "Default", contextInRule1);
        expectedSql.addRule(expectedSql.getTableName("Default", true), contextInRule1);
	sqlToIptables(false, "Default");
	assertState(false, true, true, true);

	commands.addRule(RulesTableType.OUT, "Default", ipOutRule1);
        expectedSql.addRule(expectedSql.getTableName("Default", false), ipOutRule1);
	sqlToIptables(false, "Default");
	assertState(false, true, true, true);

	commands.deleteRule(RulesTableType.IN, "Default", 1);
        expectedSql.deleteRule(expectedSql.getTableName("Default", true), 1);
        expectedSql.deleteRuleComplete(true);
	sqlToIptables(false, "Default");
	assertState(false, true, true, true);

	commands.deleteRule(RulesTableType.IN, "Default", 1);
        expectedSql.deleteRule(expectedSql.getTableName("Default", true), 1);
        expectedSql.deleteRuleComplete(true);
	sqlToIptables(false, "Default");
	assertState(false, true, true, true);

	commands.addRule(RulesTableType.IN, "Default", ipInRule1);
        expectedSql.addRule(expectedSql.getTableName("Default", true), ipInRule1);
	commands.addRule(RulesTableType.IN, "Default", contextInRule1);
        expectedSql.addRule(expectedSql.getTableName("Default", true), contextInRule1);
	commands.addRule(RulesTableType.IN, "Default", ipInRule2);
        expectedSql.addRule(expectedSql.getTableName("Default", true), ipInRule2);
	commands.addRule(RulesTableType.IN, "Default", contextInRule2);
        expectedSql.addRule(expectedSql.getTableName("Default", true), contextInRule2);
	commands.addRule(RulesTableType.OUT, "Default", contextOutRule1);
        expectedSql.addRule(expectedSql.getTableName("Default", false), contextOutRule1);
	sqlToIptables(false, "Default");
	assertState(false, true, true, true);
    }


    //////////////////////////////////////////////////////////////////////
    //////// addProtocol, deleteProt, updateProt, getProtocols    ////////
    //////////////////////////////////////////////////////////////////////

    // Test methods in normal circumstances
    public void testProtocolMethods() throws Exception {
        ArrayList<Protocol> expectedProtocols = new ArrayList<Protocol>();

        for (int i = 0; i < 200; ++i) {
            // actions:
            // 0 - add
            // 1 - remove
            // 2 - update
            int action = random.nextInt(3);
            if (expectedProtocols.size() == 0) {
                action = 0;
            }
            
            String inRegexp;
            String outRegexp;
            String name;
            Protocol p;
            int index;
            switch (action) {
                case 0: // add
                    name = randomUtils.getRandomString(5 + i);
                    inRegexp = randomUtils.getRandomString(10 + random.nextInt(20));
                    outRegexp = randomUtils.getRandomString(10 + random.nextInt(20));
                    p = new Protocol(name, inRegexp, outRegexp);

                    commands.addProtocol(p);
                    expectedProtocols.add(p);
                    break;

                case 1: // remove
                    index = random.nextInt(expectedProtocols.size());
                    p = expectedProtocols.remove(index);
                    commands.deleteProtocol(p.protocol);
                    break;
                   
                case 2: // update
                    index = random.nextInt(expectedProtocols.size());
                    name = expectedProtocols.get(index).protocol;
                    inRegexp = randomUtils.getRandomString(10 + random.nextInt(20));
                    outRegexp = randomUtils.getRandomString(10 + random.nextInt(20));
                    p = new Protocol(name, inRegexp, outRegexp);
                    commands.updateProtocol(p);

                    expectedProtocols.get(index).inReg = inRegexp;
                    expectedProtocols.get(index).outReg = outRegexp;
                    break;
            }
            assertEquals(expectedProtocols, mySql.protocols);
            assertEquals(
                    expectedProtocols, 
                    new ArrayList<Protocol>(Arrays.asList(commands.getProtocols())));
            assertIptablesEmpty();
        }
    }

    // Test addProtocol fails when adding a protocol that already exists
    public void testAddProtocol_existingProtocol() throws Exception {
        // add the same protocol twice. MYSQLCommMock will throw an exception
        commands.addProtocol(new Protocol("blah", "in", "out"));
        try {
            commands.addProtocol(new Protocol("blah", "in", "out"));
            fail("testAddProtocol_existingProtocol: expected addProtocol to throw " + 
                    "but it didn't");
        } catch (FirewallException e) {}

        try {
            commands.addProtocol(new Protocol("blah", "newin", "newout"));
            fail("testAddProtocol_existingProtocol: expected addProtocol to throw " + 
                    "but it didn't");
        } catch (FirewallException e) {}

        assertIptablesEmpty();
    }

    // Test addProtocol fails when using a regexp that's too long
    public void testAddProtocol_longRegexp() throws Exception {
        String inRegexp = randomUtils.getRandomString(Commands.MAX_REGEXP_SIZE + 1);
        String outRegexp = randomUtils.getRandomString(Commands.MAX_REGEXP_SIZE + 1);
        try {
            commands.addProtocol(new Protocol("blah", inRegexp, "newout"));
            fail("testAddProtocol_longRegexp: expected addProtocol to throw " + 
                    "but it didn't");
        } catch (FirewallException e) {}

        try {
            commands.addProtocol(new Protocol("blah", "newin", outRegexp));
            fail("testAddProtocol_longRegexp: expected addProtocol to throw " + 
                    "but it didn't");
        } catch (FirewallException e) {}

        try {
            commands.addProtocol(new Protocol("blah", inRegexp, outRegexp));
            fail("testAddProtocol_longRegexp: expected addProtocol to throw " + 
                    "but it didn't");
        } catch (FirewallException e) {}

        assertIptablesEmpty();
    }

    // Test addProtocol fails when MYSQL fails
    public void testAddProtocol_exception() throws Exception {
        mySql.failNextRequest();
        try {
            commands.addProtocol(new Protocol("blah", "newin", "newout"));
            fail("testAddProtocol_exception: expected addProtocol to throw " + 
                    "but it didn't");
        } catch (FirewallException e) {}
    }

    // test it's ok to delete a protocol that doesn't exist
    public void testDeleteProtocol_nonExistant() throws Exception {
        commands.deleteProtocol("blah");
    }

    // Test deleteProtocol fails when MYSQL fails
    public void testDeleteProtocol_exception() throws Exception {
        commands.addProtocol(new Protocol("blah", "in", "out"));
        mySql.failNextRequest();
        try {
            commands.deleteProtocol("blah");
            fail("testDeleteProtocol_exception: expected deleteProtocol to throw " + 
                    "but it didn't");
        } catch (FirewallException e) {}
     }

    // Test updateProtocol fails when MYSQL fails
    public void testUpdateProtocol_exception() throws Exception {
        commands.addProtocol(new Protocol("blah", "in", "out"));
        mySql.failNextRequest();
        try {
            commands.updateProtocol(new Protocol("blah", "newin", "newout"));
            fail("testUpdateProtocol_exception: expected updateProtocol to throw " + 
                    "but it didn't");
        } catch (FirewallException e) {}
     }

    // test it's ok to update a protocol that doesn't exist
    public void testUpdateProtocol_nonExistant() throws Exception {
        commands.updateProtocol(new Protocol("blah", "inreg", "outreg"));
    }

    // Test getProtocols fails when MYSQL fails
    public void testGetProtocols_exception() throws Exception {
        mySql.failNextRequest();
        try {
            commands.getProtocols();
            fail("testGetProtocols_exception: expected getProtocols to throw " + 
                    "but it didn't");
        } catch (FirewallException e) {}

        mySql.failNextRequest();
        try {
            commands.addProtocol(new Protocol("blah", "in", "out"));
            commands.getProtocols();
            fail("testGetProtocols_exception: expected getProtocols to throw " + 
                    "but it didn't");
        } catch (FirewallException e) {}
     }


    //////////////////////////////////////////////////////////////////////
    ////////                      deleteRuleSet                   ////////
    //////////////////////////////////////////////////////////////////////

    // Test deleteRuleSet in normal circumstances.
    public void testDeleteRuleSet() throws Exception {

        // Create ruleset
        commands.createRuleSet("blah");
        commands.createRuleSet("mooky");
        expectedSql.createRuleSet("blah");
        expectedSql.createRuleSet("mooky");
        assertEquals(expectedSql.rulesets, mySql.rulesets);

        // Add a rule
        Rule rule = new Rule(1, "1.2.3.4", 80, Rule.permissionOpt.ALLOW);
        commands.addRule(RulesTableType.IN, "blah", rule);
        commands.addRule(RulesTableType.IN, "mooky", rule);
        expectedSql.addRule(expectedSql.getTableName("blah", true), rule);
        expectedSql.addRule(expectedSql.getTableName("mooky", true), rule);
        assertEquals(expectedSql.rulesets, mySql.rulesets);
        assertIptablesEmpty();

        // Delete the ruleset
        commands.deleteRuleSet("blah");
        expectedSql.deleteRuleSet("blah");
        assertEquals(expectedSql.rulesets, mySql.rulesets);
        assertIptablesEmpty();
    }

    // Check the deleteRuleSet correctly handles SQL exception
    public void testDeleteRuleSet_SQLException() throws Exception {
        commands.createRuleSet("blah");

        mySql.failNextRequest();
        try {
            commands.deleteRuleSet("blah"); // this will throw an exception
            fail("testDeleteRuleSet_SQLException: expected deleteRuleSet to " +
                 "throw but it didn't");
        } catch (FirewallException e) {}
    }

    // Check that deleteRuleSet throws when trying to delete "Default"
    public void testDeleteRuleSet_deleteDefault() throws Exception {
        try {
            commands.deleteRuleSet("Default"); // this will throw an exception
            fail("testDeleteRuleSet_createDefault: expected deleteRuleSet to " +
                 "throw but it didn't");
        } catch (FirewallException e) {}
    }

    // Check that deleteRuleSet doesn't throw when deleting a ruleset that
    // doesn't exist
    public void testDeleteRuleSet_deleteNonExistantRuleset() throws Exception {
        commands.deleteRuleSet("blah");
    }

    //////////////////////////////////////////////////////////////////////
    ////////                      createRuleSet                   ////////
    //////////////////////////////////////////////////////////////////////

    // Test createRuleSet in normal circumstances.
    // Add a rule to our new ruleset.
    public void testCreateRuleSet() throws Exception {

        // Create ruleset
        commands.createRuleSet("blah");
        expectedSql.createRuleSet("blah");
        assertEquals(expectedSql.rulesets, mySql.rulesets);

        // Add a rule
        Rule rule = new Rule(1, "1.2.3.4", 80, Rule.permissionOpt.ALLOW);
        commands.addRule(RulesTableType.IN, "blah", rule);
        expectedSql.addRule(expectedSql.getTableName("blah", true), rule);
        assertEquals(expectedSql.rulesets, mySql.rulesets);
        assertIptablesEmpty();

        // Delete it
        commands.deleteRule(RulesTableType.IN, "blah", 1);
        expectedSql.deleteRule(expectedSql.getTableName("blah", true), 1);
        expectedSql.deleteRuleComplete(true);
        assertEquals(expectedSql.rulesets, mySql.rulesets);
        assertIptablesEmpty();
    }

    // Check the createRuleSet correctly handles SQL exception
    public void testCreateRuleSet_SQLException() throws Exception {
        mySql.failNextRequest();
        try {
            commands.createRuleSet("blah"); // this will throw an exception
            fail("testCreateRuleSet_SQLException: expected createRuleSet to " +
                 "throw but it didn't");
        } catch (FirewallException e) {}
    }

    // Check that createRuleSet throws when trying to create "Default"
    public void testCreateRuleSet_createDefault() throws Exception {
        try {
            commands.createRuleSet("Default"); // this will throw an exception
            fail("testCreateRuleSet_createDefault: expected createRuleSet to " +
                 "throw but it didn't");
        } catch (FirewallException e) {}
    }

    // Check that createRuleSet fails when creating a RuleSet that already exists
    public void testCreateRuleSet_createExistingRuleSet() throws Exception {
        commands.createRuleSet("blah");
        try {
            commands.createRuleSet("blah"); // this will throw an exception
            fail("testCreateRuleSet_createExistingRuleSet: expected createRuleSet to " +
                 "throw but it didn't");
        } catch (FirewallException e) {}
    }

    //////////////////////////////////////////////////////////////////////
    ////////                      getRuleSets                     ////////
    //////////////////////////////////////////////////////////////////////

    public void testGetRuleSets() throws Exception {
        ArrayList<String> expected = new ArrayList<String>();
        expected.add("Default");
        assertTrue(
                Arrays.equals((String[])expected.toArray(new String[expected.size()]), 
                    commands.getRuleSets()));

        commands.createRuleSet("mooky");
        expected.add(0, "mooky");
        assertTrue(
                Arrays.equals((String[])expected.toArray(new String[expected.size()]), 
                    commands.getRuleSets()));

        commands.createRuleSet("blah");
        expected.add(1, "blah");
        assertTrue(
                Arrays.equals((String[])expected.toArray(new String[expected.size()]), 
                    commands.getRuleSets()));

        commands.deleteRuleSet("mooky");
        expected.remove("mooky");
        assertTrue(
                Arrays.equals((String[])expected.toArray(new String[expected.size()]), 
                    commands.getRuleSets()));

        commands.deleteRuleSet("blah");
        expected.remove("blah");
        assertTrue(
                Arrays.equals((String[])expected.toArray(new String[expected.size()]), 
                    commands.getRuleSets()));
    }

    // check that getRuleSets() throws when SQL fails
    public void testGetRuleSets_exception() throws Exception {
        mySql.failNextRequest();
        try {
            commands.getRuleSets(); // this will throw an exception
            fail("testGetRuleSets_exception: expected getRuleSets to " +
                 "throw but it didn't");
        } catch (FirewallException e) {}
    }

    //////////////////////////////////////////////////////////////////////
    ////////                  getCurrentRuleSet                   ////////
    //////////////////////////////////////////////////////////////////////

    public void testGetCurrentRuleset() throws Exception {
        assertTrue(commands.getCurrentRuleSet().compareToIgnoreCase("Default") == 0);
        commands.createRuleSet("mooky");
        assertTrue(commands.getCurrentRuleSet().compareToIgnoreCase("Default") == 0);
        commands.createRuleSet("blah");
        assertTrue(commands.getCurrentRuleSet().compareToIgnoreCase("Default") == 0);
        commands.setRuleSet("blah");
        assertTrue(commands.getCurrentRuleSet().compareTo("blah") == 0);
        commands.setRuleSet("mooky");
        assertTrue(commands.getCurrentRuleSet().compareTo("mooky") == 0);
        commands.setRuleSet("Default");
        assertTrue(commands.getCurrentRuleSet().compareToIgnoreCase("Default") == 0);
        commands.deleteRuleSet("mooky");
        assertTrue(commands.getCurrentRuleSet().compareToIgnoreCase("Default") == 0);
        commands.setRuleSet("blah");
        assertTrue(commands.getCurrentRuleSet().compareTo("blah") == 0);
    }

    //////////////////////////////////////////////////////////////////////
    ////////                     getLogs                          ////////
    //////////////////////////////////////////////////////////////////////

    // test getLogs reports what it was given
    public void testGetLogs() throws Exception {
    }

    // test getLogs passes correct parameters
    public void testGetLogs_args() throws Exception {
	for (int i = 0; i < 100; ++i) {
	    int days = random.nextInt();
	    Log.typeOpt type = random.nextBoolean() ? Log.typeOpt.IP : Log.typeOpt.CONTEXT;
	    String protocol = randomUtils.getRandomString(random.nextInt(10));
	    String ip = randomUtils.getRandomString(random.nextInt(10));
	    Integer port = random.nextInt();
	    Log.directOpt dir = random.nextBoolean() ? Log.directOpt.IN : Log.directOpt.OUT;

	    commands.getLogs(days, type, protocol, ip, port, dir);
	    assertEquals(days, mySql.lastLogDays);
	    assertEquals(type, mySql.lastLogType);
	    assertEquals(protocol, mySql.lastLogProtocol);
	    assertEquals(ip, mySql.lastLogIp);
	    assertEquals(port, mySql.lastLogPort);
	    assertEquals(dir, mySql.lastLogDir);
	}
    }

    // Check getLogs reports an exception if thrown
    public void testLogs_Exception() throws Exception {
	mySql.failNextRequest();
	try {
            commands.getLogs(7, Log.typeOpt.IP, "blah", "1.2.3.4", 25, Log.directOpt.OUT);
            fail("testLogs_Exception: expected getLogs to throw " + 
                    "but it didn't");
        } catch (FirewallException e) {}
    }

    //////////////////////////////////////////////////////////////////////
    ////////             getGlobalLogs, addGlobalLog              ////////
    //////////////////////////////////////////////////////////////////////

    // Test addGlobalLog and getGlobalLogs in normal circumstances
    public void testGlobalLogs() throws Exception {

	assertEquals(expectedSql.globalLog, mySql.globalLog);
	assertTrue(Arrays.equals(expectedSql.getGlobalLogs(7), commands.getGlobalLogs(7)));

	expectedSql.addGlobalLog("hello message 1");
	commands.addGlobalLog("hello message 1");
	assertEquals(expectedSql.globalLog, mySql.globalLog);
	assertTrue(Arrays.equals(expectedSql.getGlobalLogs(7), commands.getGlobalLogs(7)));

	expectedSql.addGlobalLog("blah blah blah");
	commands.addGlobalLog("blah blah blah");
	assertEquals(expectedSql.globalLog, mySql.globalLog);
	assertTrue(Arrays.equals(expectedSql.getGlobalLogs(7), commands.getGlobalLogs(7)));

	expectedSql.addGlobalLog("1234567890");
	commands.addGlobalLog("1234567890");
	assertEquals(expectedSql.globalLog, mySql.globalLog);
	assertTrue(Arrays.equals(expectedSql.getGlobalLogs(7), commands.getGlobalLogs(7)));

	expectedSql.addGlobalLog("once upon a time");
	commands.addGlobalLog("once upon a time");
	assertEquals(expectedSql.globalLog, mySql.globalLog);
	assertTrue(Arrays.equals(expectedSql.getGlobalLogs(7), commands.getGlobalLogs(7)));
    }

    // Make sure getGlobalLogs sends the correct days parameter
    public void testGetGlobalLogs_arg() throws Exception {
	for (int i = 0; i < 100; ++i) {
	    int days = random.nextInt();
	    commands.getGlobalLogs(days);
	    assertEquals(days, mySql.lastGetGlobalLogsDays);
	}
    }

    // Check getGlobalLogs reports an exception if thrown
    public void testGetGlobalLogs_Exception() throws Exception {
	mySql.failNextRequest();
	try {
            commands.getGlobalLogs(7);
            fail("testGetGlobalLogs_Exception: expected getGlobalLogs to throw " + 
                    "but it didn't");
        } catch (FirewallException e) {}
    }

    //////////////////////////////////////////////////////////////////////
    ////////                      Private stuff                   ////////
    //////////////////////////////////////////////////////////////////////


    private void assertIptablesEmpty() {
        assertEquals(iptables.currentInRules.size(), 0);
        assertEquals(iptables.currentOutRules.size(), 0);
    }

    private void assertSqlEmpty() {
        assertEquals(mySql.rules.entrySet().size(), 0);
    }

    private void addSomeProtocols() throws Exception {
	commands.addProtocol(new Protocol("http", "http in", "http out"));
	expectedSql.addProtocol(new Protocol("http", "http in", "http out"));
	commands.addProtocol(new Protocol("smtp", "smtp in", "smtp out"));
	expectedSql.addProtocol(new Protocol("smtp", "smtp in", "smtp out"));
	commands.addProtocol(new Protocol("pop3", "pop3 in", "pop3 out"));
	expectedSql.addProtocol(new Protocol("pop3", "pop3 in", "pop3 out"));
	commands.addProtocol(new Protocol("telnet", "telnet in", "telnet out"));
	expectedSql.addProtocol(new Protocol("telnet", "telnet in", "telnet out"));
    }

    private void assertState(boolean log, boolean on, boolean checkSql, boolean checkIpt) {
	assertEquals(config.getIsLogActive(), log);
	assertEquals(commands.isLogEnabled(), log);

	assertEquals(config.getIsFirewallOn(), on);
	assertEquals(commands.isOn(), on);

	if (checkIpt) {
	    assertEquals(expectedIptables.currentInRules, iptables.currentInRules);
	    assertEquals(expectedIptables.currentOutRules, iptables.currentOutRules);
	}

	if (checkSql) {
	    assertEquals(expectedSql.rules, mySql.rules);
	}
    }

    private void sqlToIptables(boolean log, String activeRuleset) throws Exception {
	ArrayList<Rule> inRules = 
	    mySql.rules.get(expectedSql.getTableName(activeRuleset, true));
	ArrayList<Rule> outRules = 
	    mySql.rules.get(expectedSql.getTableName(activeRuleset, false));

	expectedIptables.reset();

	for (Rule rule : inRules) {
	    if (rule.type == Rule.typeOpt.IP) {
		expectedIptables.add(RulesTableType.IN, rule, log, null);
	    } else {
		expectedIptables.add(
			RulesTableType.IN, 
			rule, 
			log, 
			getRegexp(rule.protocol, RulesTableType.IN));
	    }
	}

    	for (Rule rule : outRules) {
	    if (rule.type == Rule.typeOpt.IP) {
		expectedIptables.add(RulesTableType.OUT, rule, log, null);
	    } else {
		expectedIptables.add(
			RulesTableType.OUT, 
			rule, 
			log, 
			getRegexp(rule.protocol, RulesTableType.OUT));
	    }
	}
    }

    private String getRegexp(String name, RulesTableType dir) throws Exception {
	Protocol protocol = mySql.getProtocol(name);
	if (dir == RulesTableType.IN) {
	    return protocol.inReg;
	}

	return protocol.outReg;
    }

    public static Test suite(String[] args) {
	if (args.length == 0) {
	    return new TestSuite(CommandsTest.class);
	} else {
	    TestSuite testsToRun = new TestSuite();
	    for (int i = 0; i < args.length; ++i) {
		testsToRun.addTest(new CommandsTest(args[i]));
	    }
	    return testsToRun;
	}
    }
}
